package com.example.demo.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * redis分布式锁的简单实现
 * <p>
 * 1 支持获取锁失败的时候自旋等待
 * 2 自定义锁的过期时间，可以结合业务代码执行时间进行调整
 * 3 自定义锁的key和val，解锁时比较val避免误删除
 * 4 使用lua脚本，保证了redis指令集操作的原子性
 * 5 不支持锁的自动续期
 * 6 是不可重入锁
 *
 * @author MarioZzz
 * @create 2024-05-16 14:19
 */
@Slf4j
public class SimplyRedisLock implements ILock {

    public SimplyRedisLock(StringRedisTemplate stringRedisTemplate, String redisKey, String redisVal, long timeout, TimeUnit unit) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.redisKey = redisKey;
        this.redisVal = redisVal;
        this.timeout = timeout;
        this.unit = unit;
    }

    public SimplyRedisLock(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private final StringRedisTemplate stringRedisTemplate;

    private String redisKey;

    private String redisVal;

    private long timeout;

    private TimeUnit unit;

    private static final String LUA_SCRIPT_GET_DEL = "if (redis.call('GET', KEYS[1]) == ARGV[1]) then\n" +
            "  return redis.call('DEL', KEYS[1])\n" +
            "end\n" +
            "return 0";

    /**
     * 支持多系统的 加锁 ,同一时刻只有一个系统竞争成功
     */
    public <T> void lockButOnlyOneSuccess(String redisKey, String redisVal, long timeout, TimeUnit unit, Consumer<T> consumer, T param) {
        boolean isExist;
        try {
            isExist = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, redisVal, timeout, unit);
            if (isExist) {
                consumer.accept(param);
            } else {
                log.error("获取锁失败，本次业务将由其他系统执行，redisKey==={},redisVal==={}", redisKey, redisVal);
            }
        } catch (Exception e) {
            log.error("生成redis分布式锁出现异常", e);
        } finally {
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LUA_SCRIPT_GET_DEL, Long.class);
            stringRedisTemplate.execute(redisScript, Collections.singletonList(redisKey), redisVal);
        }
    }

    public <T> void waitUntilLockSuccess(Consumer<T> consumer, T param) {
        try {
            //自旋等待
            while (!this.tryLock()) {
                try {
                    log.error("获取锁失败自旋等待中，本次业务将由其他系统执行，redisKey==={},redisVal==={}", redisKey, redisVal);
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //加锁成功
            consumer.accept(param);
        } catch (Exception e) {
            log.error("", e);
        } finally {
            unlock();
        }
    }

    @Override
    public boolean tryLock() {
        return stringRedisTemplate.opsForValue().setIfAbsent(redisKey, redisVal, timeout, unit);
    }

    @Override
    public void unlock() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LUA_SCRIPT_GET_DEL, Long.class);
        stringRedisTemplate.execute(redisScript, Collections.singletonList(redisKey), redisVal);
    }
}
